﻿using System;
using System.Collections.Generic;
using System.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a new session.
    /// </summary>
    public class CreateSession : CreateAuditable<SessionEntity>
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public CreateSession()
        {
            AccessedOn = DateTime.UtcNow;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the account identifier.
        /// </summary>
        public int AccountID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public string Source
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SessionFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the session last access date and time.
        /// </summary>
        public DateTime AccessedOn
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(Source))
                return Err.Of<SessionEntity>().NotValid("Source");
            if(!uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = AccountID
            }).Result.Flags.HasFlag(AccountFlags.Active))
            {
                return Err.Of<AccountEntity>().NotAvailable();
            }
            return base.Validate(uow);
        }

        protected override void Construct(IUnitOfWork uow, SessionEntity entity)
        {
            base.Construct(uow, entity);

            entity.Guid = Guid.NewGuid();
            entity.Account = uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = AccountID
            }).Result;
            entity.Source = Source.TrimToLen(100);
            entity.Flags = Flags;
            entity.AccessedOn = AccessedOn;
        }

        #endregion
    }

    /// <summary>
    /// Updates the session.
    /// </summary>
    public class UpdateSession : UpdateAuditable<SessionEntity>
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public UpdateSession()
        {
            AccessedOn = DateTime.UtcNow;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public string Source
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SessionFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the session last access date and time.
        /// </summary>
        public DateTime AccessedOn
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(Source))
                return Err.Of<SessionEntity>().NotValid("Source");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, SessionEntity entity)
        {
            base.Revise(uow, entity);

            entity.Source = Source.TrimToLen(100);
            entity.AccessedOn = AccessedOn;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the session.
    /// </summary>
    public class DeleteSession : DeleteEntity<SessionEntity>
    {
    }

    /// <summary>
    /// Gets the session by identifier.
    /// </summary>
    public class GetSession : GetEntity<SessionEntity>,
        IApiOperation
    {
    }

    /// <summary>
    /// Gets the session by the global unique identifier.
    /// </summary>
    public class GetSessionByGuid : QueryEntity<SessionEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the global unique identifier.
        /// </summary>
        public Guid Guid
        {
            get;
            set;
        }

        #endregion

        #region Options

        protected override bool Cacheable
        {
            get
            {
                return true;
            }
        }

        #endregion

        #region Utilities

        protected override IQueryable<SessionEntity> Build(IUnitOfWork uow, IQueryable<SessionEntity> query)
        {
            return base.Build(uow, query).Where(x => x.Guid == Guid);
        }

        #endregion
    }

    /// <summary>
    /// Gets the session by account identifier and source.
    /// </summary>
    public class GetSessionByAccountAndSource : QueryEntity<SessionEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the account identifier.
        /// </summary>
        public int AccountID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public string Source
        {
            get;
            set;
        }

        #endregion

        #region Options

        protected override bool Cacheable
        {
            get
            {
                return true;
            }
        }

        #endregion

        #region Utilities

        protected override IQueryable<SessionEntity> Build(IUnitOfWork uow, IQueryable<SessionEntity> query)
        {
            string source = StringHelper.EnsureNotNull(Source).TrimToLen(100);
            return base.Build(uow, query).Where(x =>
                x.Account.EntityID == AccountID &&
                x.Source.Equals(source));
        }


        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over session collection.
    /// </summary>
    public class SearchSessions : SearchOperation<SessionEntity>
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchSessions()
        {
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                SessionSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the account identifier.
        /// </summary>
        public int? AccountID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the last access date "from".
        /// </summary>
        public DateTime? AccessedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the last access date "to".
        /// </summary>
        public DateTime? AccessedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date "from".
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date "to".
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public SessionFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public SessionSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SessionEntity> Build(IUnitOfWork uow, IQueryable<SessionEntity> query)
        {
            query = base.Build(uow, query);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByDateRange(x => x.AccessedOn, AccessedFrom, AccessedTo);
            query = query.FilterByDateRange(x => x.CreatedOn, CreatedFrom, CreatedTo);
            if(AccountID.HasValue)
                query = query.Where(x => x.Account.EntityID == AccountID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case SessionSortBy.AccessedOn:
                            query = query.OrderBy(x => x.AccessedOn, SortDir, i != 0);
                            break;
                        case SessionSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Bulk__

    /// <summary>
    /// Bulk delete sessions.
    /// </summary>
    public class CloseSessions : Operation<Int32>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the last access date "from".
        /// </summary>
        public DateTime? AccessedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the last access date "to".
        /// </summary>
        public DateTime? AccessedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date "from".
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date "to".
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out int result)
        {
            result = 0;
            foreach(var i in uow.Execute(new SearchSessions
            {
                AccessedFrom = AccessedFrom,
                AccessedTo = AccessedTo,
                CreatedFrom = CreatedFrom,
                CreatedTo = CreatedTo
            }).Result)
            {
                uow.Execute(new CloseSession
                {
                    EntityID = i.EntityID
                });
                result++;
            }
        }

        #endregion
    }

    #endregion

    #region __Workflow__

    /// <summary>
    /// Opens a session.
    /// </summary>
    public class OpenSession : Operation<SessionEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the account identity.
        /// </summary>
        public string Identity
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public string Source
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the account password.
        /// </summary>
        public string Password
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SessionFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out SessionEntity result)
        {
            var account = uow.Execute(new GetAccountByEmail
            {
                Email = Identity
            }).Result;
            if(account == null || !uow.Execute(new ValidateAccountPassword
            {
                EntityID = account.EntityID,
                Password = Password
            }).Result)
            {
                throw Err.Of(this).NotValid("Credentials").Ex();
            }

            result = uow.Execute(new GetSessionByAccountAndSource
            {
                AccountID = account.EntityID,
                Source = Source
            }).Result ?? uow.Execute(new CreateSession
            {
                AccountID = account.EntityID,
                Source = Source,
                Flags = Flags,
                AccessedOn = DateTime.UtcNow
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Connects to a session.
    /// </summary>
    public class ConnectToSession : Operation<SessionEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the global unique identifier.
        /// </summary>
        public Guid Guid
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out SessionEntity result)
        {
            var entity = uow.Execute(new GetSessionByGuid
            {
                Guid = Guid
            }).Result;
            if(entity != null)
            {
                int sessionUpdatePeriod = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of(this).Name("UpdatePeriod")
                }).ResultAs(5);
                var dtNow = DateTime.UtcNow;

                if(dtNow.Subtract(entity.AccessedOn).Minutes >= sessionUpdatePeriod)
                {
                    entity = uow.Execute(new UpdateSession
                    {
                        EntityID = entity.EntityID,
                        Source = entity.Source,
                        Flags = entity.Flags,
                        AccessedOn = dtNow
                    }).Result;
                }
            }
            result = entity;
        }

        #endregion
    }

    /// <summary>
    /// Closes the session.
    /// </summary>
    public class CloseSession : DeleteSession,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("ACCNT");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .Rule((ctx, uow) => ctx.Session(uow).EntityID == EntityID);
            }
        }

        #endregion
    }

    #endregion
}
